package org.refresh.core.intelligence.emf;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.ocl.OCL;
import org.eclipse.ocl.Query;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.helper.OCLHelper;
import org.refresh.core.intelligence.Constraint;
import org.refresh.core.intelligence.ConstraintEvaluationError;
import org.refresh.core.intelligence.ConstraintEvaluator;
import org.refresh.core.intelligence.QueryEvaluator;
import org.refresh.core.intelligence.UnsupportedConstraintTypeException;

/*******************************************************************************
 * Copyright (c) 2005, 2006 Jules White. All rights reserved. This program and
 * the accompanying materials are made available under the terms of the Eclipse
 * Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: Jules White - initial API and implementation
 ******************************************************************************/
public class OCLConstraintEvaluator implements ConstraintEvaluator, QueryEvaluator {

	Map<EClass, List<EObject>> extents_ = new HashMap<EClass, List<EObject>>(2);

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.refresh.core.intelligence.ConstraintEvaluator#evaluate(java.lang.Object,
	 *      java.lang.Object)
	 */
	public boolean evaluate(Object src, Object trg, Constraint constraint) {
		ArrayList args = new ArrayList();
		args.add(trg);
		Collection result = evaluateImpl(src, args, constraint);
		return result != null && result.size() > 0;
	}
	
	public Collection evaluateImpl(Object src, List<Object> trgs, Constraint constraint) {
		if (!(constraint instanceof OCLConstraint))
			throw new UnsupportedConstraintTypeException(constraint, this);

		Collection result = null;
		OCLConstraint oclcon = (OCLConstraint) constraint;

		Map<EClass, Set<? extends EObject>> extents = new HashMap<EClass, Set<? extends EObject>>();
		
		EObject seobj = (EObject) src;
		
		for(Object o : trgs){
			EObject teobj = (EObject) o;
			addObject(extents,teobj);
		}
		

		String tname = seobj.eClass().getName();
		String tvar = seobj.eClass().getName().toLowerCase();
		String qstr = tname + ".allInstances()->select( target : " + tname
				+ " | " + oclcon.getOcl() + ")";
		
		
		OCL ocl = OCL.newInstance(EcoreEnvironmentFactory.INSTANCE);
		ocl.setExtentMap(extents);
		OCLHelper helper = ocl.createOCLHelper();

		helper.setContext(seobj.eClass());

		try {
			OCLExpression expr = helper.createQuery(qstr);
			Query query = ocl.createQuery(expr);

			Object rslt = query.evaluate(seobj);
			if(rslt instanceof Collection)
				result = (Collection)rslt;
			
		} catch (Exception e) {
			throw new ConstraintEvaluationError(oclcon,e);
		}
		return result;
	}

	
	public void addObject(Map<EClass, Set<? extends EObject>> map, EObject obj){
		EList<EClass> supers = obj.eClass().getEAllSuperTypes();
		EClass type = obj.eClass();
		for(EClass sup : supers){
			Set set = getTypeSet(map, sup);
			set.add(obj);
		}
		Set set = getTypeSet(map, type);
		set.add(obj);
	}
	
	public Set<? extends EObject> getTypeSet(Map<EClass, Set<? extends EObject>> map, EClass type){
		Set<? extends EObject> l = map.get(type);
		if(l == null){
			l = new HashSet<EObject>();
			map.put(type, l);
		}
		return l;
	}

	/* (non-Javadoc)
	 * @see org.refresh.core.intelligence.QueryEvaluator#findAll(java.lang.Object, java.util.List, java.util.List)
	 */
	public List findAll(Object src, List world, List<Constraint> cons) {
		for(Constraint c : cons){
			Collection result = evaluateImpl(src, world, c);
			world.clear();
			world.addAll(result);
		}
		return world;
	}

	
}
